package com.atlin.project.controller;

import cn.hutool.core.io.FileUtil;
import com.atlin.project.annotation.AuthCheck;
import com.atlin.project.bizmq.bi.BiMessageProducer;
import com.atlin.project.common.BaseResponse;
import com.atlin.project.common.DeleteRequest;
import com.atlin.project.common.ErrorCode;
import com.atlin.project.common.ResultUtils;
import com.atlin.project.contant.UserConstant;
import com.atlin.project.exception.BusinessException;
import com.atlin.project.exception.ThrowUtils;
import com.atlin.project.manager.AiManager;
import com.atlin.project.manager.RedisLimiterManager;
import com.atlin.project.model.domain.BiTeam;
import com.atlin.project.model.domain.BiUserTeam;
import com.atlin.project.model.domain.chart.Chart;
import com.atlin.project.model.domain.SysUser;
import com.atlin.project.model.domain.chart.ChartOriginalData;
import com.atlin.project.model.dto.chart.*;
import com.atlin.project.model.enums.chart.ChartSourceEnum;
import com.atlin.project.model.enums.chart.ChartStatusEnum;
import com.atlin.project.model.enums.chart.ChartTypeEnum;
import com.atlin.project.model.vo.chart.BiResponse;
import com.atlin.project.model.vo.chart.ChartEditVO;
import com.atlin.project.model.vo.chart.ChartVO;
import com.atlin.project.service.BiTeamService;
import com.atlin.project.service.BiUserTeamService;
import com.atlin.project.service.ChartService;
import com.atlin.project.service.SysUserService;
import com.atlin.project.utils.ExcelUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * 图表接口
 *
 * @author atlin
 */
@RestController
@RequestMapping("/chart")
@Slf4j
public class ChartController {

    @Resource
    private ChartService chartService;

    @Resource
    private SysUserService sysUserService;

    @Resource
    private BiUserTeamService biUserTeamService;

    @Resource
    private BiTeamService biTeamService;

    @Resource
    AiManager aiManager;

    @Resource
    private RedisLimiterManager redisLimiterManager;

    @Resource(name = "chart-gen-thread")
    private ThreadPoolExecutor threadPoolExecutor;

    @Resource
    private BiMessageProducer biMessageProducer;

    private final static Gson GSON = new Gson();

    // region 增删改查


    /**
     * 智能分析
     *
     * @param multipartFile
     * @param genChartAiRequest
     * @param request
     * @return
     */
    @PostMapping("/gen")
    public BaseResponse<BiResponse> genChartByAi(@RequestPart("file") MultipartFile multipartFile,
                                                 GenChartAiRequest genChartAiRequest, HttpServletRequest request) {
        if (genChartAiRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        SysUser loginUser = sysUserService.getLoginUser(request);
        redisLimiterManager.doRateLimiter("genChartByAi_" + loginUser.getId());

        String name = genChartAiRequest.getName();
        String goal = genChartAiRequest.getGoal();
        String chartType = genChartAiRequest.getChartType();
        ChartTypeEnum chartTypeEnum = ChartTypeEnum.getEnumByValue(chartType);

        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标不存在");
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
        ThrowUtils.throwIf(chartTypeEnum == null, ErrorCode.PARAMS_ERROR, "不支持此类型图表");

        // 文件校验
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();
        final long ONE_MB = 1024 * 1024L;
        final List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过1MB");
        String suffix = FileUtil.getSuffix(originalFilename);
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件类型非法");


        String csvData = ExcelUtils.excelToCsv(multipartFile);
        ThrowUtils.throwIf(StringUtils.isBlank(csvData), ErrorCode.PARAMS_ERROR, "不支持此类型图表");

        // 拼接问题
        StringBuilder userInput = new StringBuilder();
        userInput.append("目标:").append(goal)
                .append("，请使用").append(chartType).append("\n");
        userInput.append("数据:").append(csvData);

        String resultContent = aiManager.sendMsgToSpark(true, userInput.toString());

        String[] split = resultContent.split("【【【【【");
        if (split.length < 3) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "AI生成失败");
        }
        String genChart = split[1].trim();
        String genResult = split[2].trim();

        // option 校验 --- 失败重试
        boolean b = chartService.chartOptionValidator(genChart);

        // 保存数据
        Chart chart = new Chart();
        chart.setGoal(goal);
        chart.setName(name);
//        chart.setChartData(csvData);
        chart.setChartType(chartType);
        chart.setGenChart(genChart);
        chart.setGenResult(genResult);
        chart.setStatus(ChartStatusEnum.SUCCESS.getValue());
        chart.setUserId(loginUser.getId());
        boolean save = chartService.save(chart);
        if (save) {
            chartService.saveChartData(chart.getId(), csvData);
        }

        BiResponse biResponse = new BiResponse();
        biResponse.setId(chart.getId());
        biResponse.setGenChart(genChart);
        biResponse.setGenResult(genResult);
        return ResultUtils.success(biResponse);
    }

    /**
     * 智能分析（异步）
     *
     * @param multipartFile
     * @param genChartAiRequest
     * @param request
     * @return
     */
    @PostMapping("/gen/async")
    public BaseResponse<BiResponse> genChartByAiAsync(@RequestPart("file") MultipartFile multipartFile,
                                                      GenChartAiRequest genChartAiRequest, HttpServletRequest request) {
        if (genChartAiRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        SysUser loginUser = sysUserService.getLoginUser(request);
        redisLimiterManager.doRateLimiter("genChartByAi_" + loginUser.getId());

        String name = genChartAiRequest.getName();
        String goal = genChartAiRequest.getGoal();
        String chartType = genChartAiRequest.getChartType();
        ChartTypeEnum chartTypeEnum = ChartTypeEnum.getEnumByValue(chartType);

        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标不存在");
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
        ThrowUtils.throwIf(chartTypeEnum == null, ErrorCode.PARAMS_ERROR, "不支持此类型图表");

        // 文件校验
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();
        final long ONE_MB = 1024 * 1024L;
        final List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过1MB");
        String suffix = FileUtil.getSuffix(originalFilename);
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件类型非法");


        String csvData = ExcelUtils.excelToCsv(multipartFile);
        ThrowUtils.throwIf(StringUtils.isBlank(csvData), ErrorCode.PARAMS_ERROR, "不支持此类型图表");

        // 拼接问题
        StringBuilder userInput = new StringBuilder();
        userInput.append("目标:").append(goal)
                .append("，请使用").append(chartType).append("\n");
        userInput.append("数据:").append(csvData);

        // 保存数据
        Chart chart = new Chart();
        chart.setGoal(goal);
        chart.setName(name);
        chart.setChartType(chartType);
        chart.setStatus(ChartStatusEnum.QUEUEING.getValue());
        chart.setUserId(loginUser.getId());
        boolean save = chartService.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.SYSTEM_ERROR, "图表保存失败");
        // 保存原始数据
        chartService.saveChartData(chart.getId(), csvData);

        CompletableFuture.runAsync(() -> {
            Chart updateChart = new Chart();
            updateChart.setId(chart.getId());
            updateChart.setStatus(ChartStatusEnum.GENERATING.getValue());
            boolean b = chartService.updateById(updateChart);
            if (!b) {
                chartService.handleChartUpdateError(chart.getId(), "更新图表执行中状态失败");
                return;
            }
            String resultContent = aiManager.sendMsgToSpark(true, userInput.toString());
            String[] split = resultContent.split("【【【【【");
            if (split.length < 3) {
                chartService.handleChartUpdateError(chart.getId(), "AI生成失败");
                return;
            }
            String genChart = split[1].trim();
            String genResult = split[2].trim();
            // option 校验 --- 失败重试
            boolean validator = chartService.chartOptionValidator(genChart);

            // 存储得到的结果
            Chart updateChartResult = new Chart();
            updateChartResult.setId(chart.getId());
            updateChartResult.setGenChart(genChart);
            updateChartResult.setGenResult(genResult);
            updateChartResult.setStatus(ChartStatusEnum.SUCCESS.getValue());
            boolean updateResult = chartService.updateById(updateChartResult);
            if (!updateResult) {
                chartService.handleChartUpdateError(chart.getId(), "更新图表成功状态失败");
            }
        }, threadPoolExecutor);

        BiResponse biResponse = new BiResponse();
        biResponse.setId(chart.getId());
        return ResultUtils.success(biResponse);
    }

    /**
     * 智能分析（异步Mq实现）
     *
     * @param multipartFile
     * @param genChartAiRequest
     * @param request
     * @return
     */
    @PostMapping("/gen/async/mq")
    public BaseResponse<BiResponse> genChartByAiAsyncMq(@RequestPart("file") MultipartFile multipartFile,
                                                        GenChartAiRequest genChartAiRequest, HttpServletRequest request) {
        if (genChartAiRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        SysUser loginUser = sysUserService.getLoginUser(request);
        redisLimiterManager.doRateLimiter("genChartByAi_" + loginUser.getId());

        String name = genChartAiRequest.getName();
        String goal = genChartAiRequest.getGoal();
        String chartType = genChartAiRequest.getChartType();
        ChartTypeEnum chartTypeEnum = ChartTypeEnum.getEnumByValue(chartType);

        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标不存在");
        ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长");
        ThrowUtils.throwIf(chartTypeEnum == null, ErrorCode.PARAMS_ERROR, "不支持此类型图表");

        // 文件校验
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();
        final long ONE_MB = 1024 * 1024L;
        final List<String> validFileSuffixList = Arrays.asList("xlsx", "xls");
        ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过1MB");
        String suffix = FileUtil.getSuffix(originalFilename);
        ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件类型非法");

        String csvData = ExcelUtils.excelToCsv(multipartFile);
        ThrowUtils.throwIf(StringUtils.isBlank(csvData), ErrorCode.PARAMS_ERROR, "不支持此类型图表");

        // 保存数据
        Chart chart = new Chart();
        chart.setGoal(goal);
        chart.setName(name);
//        chart.setChartData(csvData);
        chart.setChartType(chartType);
        chart.setStatus(ChartStatusEnum.QUEUEING.getValue());
        chart.setUserId(loginUser.getId());
        boolean save = chartService.save(chart);
        ThrowUtils.throwIf(!save, ErrorCode.SYSTEM_ERROR, "图表保存失败");
        // 保存原始数据
        String saveChartData = chartService.saveChartData(chart.getId(), csvData);
        ThrowUtils.throwIf(StringUtils.isBlank(saveChartData), ErrorCode.SYSTEM_ERROR, "图表原始数据保存失败");
        // 发送消息
        biMessageProducer.sendMessage(String.valueOf(chart.getId()));

        BiResponse biResponse = new BiResponse();
        biResponse.setId(chart.getId());
        return ResultUtils.success(biResponse);
    }


    /**
     * 查询我的图表
     *
     * @param chartQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/list/my/page/vo")
    public BaseResponse<Page<ChartVO>> listMyChartVOByPage(@RequestBody ChartQueryRequest chartQueryRequest,
                                                           HttpServletRequest request) {
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        String name = chartQueryRequest.getName();
        String chartSource = chartQueryRequest.getChartSource();

        SysUser loginUser = sysUserService.getLoginUser(request);
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);

        ChartSourceEnum chartSourceEnum = ChartSourceEnum.getEnumByValue(chartSource);
        ThrowUtils.throwIf(chartSourceEnum == null, ErrorCode.PARAMS_ERROR);

        QueryWrapper<Chart> wrapper = new QueryWrapper<>();
        List<BiUserTeam> biUserTeams = biUserTeamService.listUserTeamByUserId(loginUser.getId());

        if (chartSourceEnum == ChartSourceEnum.INDIVIDUAL) {
            wrapper.eq(StringUtils.isNotBlank(name), "name", name);
            wrapper.eq("userId", loginUser.getId());
            if (!biUserTeams.isEmpty()) {
                List<Long> teamIdList = biUserTeams.stream().map(BiUserTeam::getTeamId).collect(Collectors.toList());
                List<BiTeam> teamList = biTeamService.list(new LambdaQueryWrapper<BiTeam>().in(BiTeam::getId, teamIdList));
                List<Long> chartIdList = teamList.stream().map(BiTeam::getChartId).collect(Collectors.toList());
                if (chartIdList.isEmpty()) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR);
                }
                wrapper.notIn("id", chartIdList);
            }
        } else {
            if (biUserTeams.isEmpty()) {
                return ResultUtils.success(new Page<>(current, size));
            }
            List<Long> teamIdList = biUserTeams.stream().map(BiUserTeam::getTeamId).collect(Collectors.toList());

            List<BiTeam> teamList = biTeamService.list(new LambdaQueryWrapper<BiTeam>().in(BiTeam::getId, teamIdList));
            if (teamList.isEmpty()) {
                return ResultUtils.success(new Page<>(current, size));
            }
            List<Long> chartIdList = teamList.stream().map(BiTeam::getChartId).collect(Collectors.toList());

            wrapper.in("id", chartIdList);
        }


        wrapper.orderByDesc("createTime");
        Page<Chart> chartPage = chartService.page(new Page<>(current, size), wrapper);

        Page<ChartVO> chartVOPage = chartService.getChartVOPage(chartPage, request);

        return ResultUtils.success(chartVOPage);
    }

    /**
     * 重试
     *
     * @param chartRetryRequest
     * @param request
     * @return
     */
    @PostMapping("/chartRetry")
    public BaseResponse<Boolean> chartRetry(@RequestBody ChartRetryRequest chartRetryRequest, HttpServletRequest request) {
        if (chartRetryRequest == null || chartRetryRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        SysUser loginUser = sysUserService.getLoginUser(request);
        long id = chartRetryRequest.getId();
        // 判断是否存在
        Chart oldchart = chartService.getById(id);
        ThrowUtils.throwIf(oldchart == null, ErrorCode.NULL_ERROR);

        // 不是自己的图表 且 不是管理员
        ThrowUtils.throwIf(!loginUser.getId().equals(oldchart.getUserId()) && !sysUserService.isAdmin(loginUser), ErrorCode.NO_AUTH_ERROR);

        Chart chart = new Chart();
        chart.setId(id);
        chart.setStatus(ChartStatusEnum.QUEUEING.getValue());
        boolean update = chartService.updateById(chart);

        ThrowUtils.throwIf(!update, ErrorCode.SYSTEM_ERROR, "重试失败");
        // 发送消息
        biMessageProducer.sendMessage(String.valueOf(chart.getId()));

        return ResultUtils.success(true);
    }


    /**
     * 创建
     *
     * @param chartAddRequest
     * @param request
     * @return
     */
    @PostMapping("/add")
    public BaseResponse<Long> addChart(@RequestBody ChartAddRequest chartAddRequest, HttpServletRequest request) {
        if (chartAddRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        SysUser loginUser = sysUserService.getLoginUser(request);
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartAddRequest, chart);

        // todo
        chart.setUserId(loginUser.getId());
        chartService.valid(chart, true);

        boolean result = chartService.save(chart);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        long newChartId = chart.getId();
        return ResultUtils.success(newChartId);
    }

    /**
     * 删除
     *
     * @param deleteRequest
     * @param request
     * @return
     */
    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteChart(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
        if (deleteRequest == null || deleteRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        SysUser user = sysUserService.getLoginUser(request);
        long id = deleteRequest.getId();
        // 判断是否存在
        Chart oldchart = chartService.getById(id);
        ThrowUtils.throwIf(oldchart == null, ErrorCode.NULL_ERROR);

        // 仅本人或管理员可删除
        if (!oldchart.getUserId().equals(user.getId()) && !sysUserService.isAdmin(request)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        boolean b = chartService.removeById(id);
        return ResultUtils.success(b);
    }

    /**
     * 更新（仅管理员）
     *
     * @param chartUpdateRequest
     * @return
     */
    @PostMapping("/update")
    public BaseResponse<Boolean> updateChart(@RequestBody ChartUpdateRequest chartUpdateRequest, HttpServletRequest request) {
        if (chartUpdateRequest == null || chartUpdateRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        SysUser loginUser = sysUserService.getLoginUser(request);
        long id = chartUpdateRequest.getId();
        // 判断是否存在
        Chart oldchart = chartService.getById(id);
        ThrowUtils.throwIf(oldchart == null, ErrorCode.NULL_ERROR);
        
        // 不是本人或管理员，不能直接获取所有信息
        boolean supportTeam = biTeamService.checkUserBelongTeam(loginUser.getId(), id);
        if (!oldchart.getUserId().equals(loginUser.getId()) && !sysUserService.isAdmin(loginUser) && !supportTeam) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }

        boolean result = chartService.updateChart(chartUpdateRequest);

        return ResultUtils.success(result);
    }

    /**
     * 根据 id 获取
     *
     * @param id
     * @return
     */
    @GetMapping("/get")
    public BaseResponse<ChartEditVO> getChartById(long id, HttpServletRequest request) {
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = chartService.getById(id);
        if (chart == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        SysUser loginUser = sysUserService.getLoginUser(request);
        BiTeam biTeam =  biTeamService.getTeamByChartId(id);
        // 是否支持团队协作
        // 不是本人或管理员，不能直接获取所有信息
        boolean supportTeam = biTeamService.checkUserBelongTeam(loginUser.getId(), id);
        if (!chart.getUserId().equals(loginUser.getId()) && !sysUserService.isAdmin(loginUser) && !supportTeam) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        
        ChartEditVO chartEditVO = new ChartEditVO();
        ChartOriginalData chartOriginalData = chartService.getChartOriginalObjData(chart.getId());
        chartEditVO.setChart(chart);
        chartEditVO.setChartOriginalData(chartOriginalData);

        return ResultUtils.success(chartEditVO);
    }

    /**
     * 根据 id 获取
     *
     * @param id
     * @return
     */
    @GetMapping("/get/vo")
    public BaseResponse<ChartVO> getChartVOById(long id, HttpServletRequest request) {
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Chart chart = chartService.getById(id);
        if (chart == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }

        ChartVO chartVO = ChartVO.objToVo(chart);
        return ResultUtils.success(chartVO);
    }

    /**
     * 分页获取列表（封装类）
     *
     * @param chartQueryRequest 查询参数
     * @param request           查询请求
     * @return 分页封装类
     */
    @PostMapping("/list/page/vo")
    public BaseResponse<Page<ChartVO>> listChartVOByPage(@RequestBody ChartQueryRequest chartQueryRequest,
                                                         HttpServletRequest request) {
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        Page<Chart> chartPage = chartService.page(new Page<>(current, size),
                new QueryWrapper<>());

        Page<ChartVO> chartVOPage = chartService.getChartVOPage(chartPage, request);

        return ResultUtils.success(chartVOPage);
    }

    /**
     * 分页获取列表（仅管理员）
     *
     * @param chartQueryRequest 查询参数
     * @param request           查询请求
     * @return 分页
     */
    @PostMapping("/list/page")
    @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
    public BaseResponse<Page<Chart>> listChartByPage(@RequestBody ChartQueryRequest chartQueryRequest,
                                                     HttpServletRequest request) {
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        Page<Chart> chartPage = chartService.page(new Page<>(current, size),
                chartService.getQueryWrapper(chartQueryRequest));
        return ResultUtils.success(chartPage);
    }

    // endregion

}
